/*
 * UAE
 *
 * Win32 audio player for CDA emulation
 *
 * Copyright 2010 Toni Wilen
 *
 */

#include "sysconfig.h"
#include "cda_play.h"

#define CDADS 0

cda_audio::~cda_audio ()
{
    wait(0);
    wait(1);
    #if CDADS
    if (dsnotify)
        dsnotify->Release();
    if (dsbuf)
        dsbuf->Release();
    if (ds)
        ds->Release();
    if (notifyevent[0])
        CloseHandle(notifyevent[0]);
    if (notifyevent[1])
        CloseHandle(notifyevent[1]);
    #else
    if (active)
    {
        for (int i = 0; i < 2; i++)
            waveOutUnprepareHeader(wavehandle, &whdr[i], sizeof(WAVEHDR));
    }
    if (wavehandle != nullptr)
        waveOutClose(wavehandle);
    #endif
    for (int i = 0; i < 2; i++)
    {
        free(buffers[i]);
        buffers[i] = nullptr;
    }
}

cda_audio::cda_audio (int num_sectors)
{
    active = false;
    playing = false;
    volume[0] = volume[1] = 0;

    bufsize = num_sectors * 2352;
    for (int i = 0; i < 2; i++)
    {
        buffers[i] = xcalloc(byte, num_sectors * 4096);
    }

    WAVEFORMATEX wav;
    memset(&wav, 0, sizeof(WAVEFORMATEX));

    wav.cbSize = 0;
    wav.nChannels = 2;
    wav.nSamplesPerSec = 44100;
    wav.wBitsPerSample = 16;
    wav.nBlockAlign = wav.wBitsPerSample / 8 * wav.nChannels;
    wav.nAvgBytesPerSec = wav.nBlockAlign * wav.nSamplesPerSec;
    wav.wFormatTag = WAVE_FORMAT_PCM;

    #if CDADS
    LPDIRECTSOUNDBUFFER pdsb;
    WAVEFORMATEXTENSIBLE wavfmt;
    DSBUFFERDESC desc;
    HRESULT hr;

    dsnotify = nullptr;
    dsbuf = nullptr;
    ds = nullptr;
    notifyevent[0] = notifyevent[1] = nullptr;

    hr = DirectSoundCreate8(NULL, &ds, nullptr);
    if (FAILED(hr))
    {
        Logger::Write(L"CDA: DirectSoundCreate8() failure: %s\n", DXError(hr));
        return;
    }

    hr = ds->SetCooperativeLevel(hMainWnd, DSSCL_PRIORITY);
    if (FAILED(hr))
    {
        Logger::Write(L"CDA: Can't set cooperativelevel: %s\n", DXError(hr));
        return;
    }

    wavfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
    wavfmt.Format.nChannels = 2;
    wavfmt.Format.nSamplesPerSec = 44100;
    wavfmt.Format.wBitsPerSample = 16;
    wavfmt.Format.nBlockAlign = wavfmt.Format.wBitsPerSample / 8 * wavfmt.Format.nChannels;
    wavfmt.Format.nAvgBytesPerSec = wavfmt.Format.nBlockAlign * wavfmt.Format.nSamplesPerSec;
    wavfmt.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
    wavfmt.Samples.wValidBitsPerSample = 16;
    wavfmt.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);

    memset(&desc, 0, sizeof desc);
    desc.dwSize = sizeof desc;
    desc.dwBufferBytes = 2 * bufsize;
    desc.lpwfxFormat = &wavfmt.Format;
    desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
    desc.dwFlags |= DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_CTRLVOLUME;
    desc.guid3DAlgorithm = GUID_NULL;

    hr = ds->CreateSoundBuffer(&desc, &pdsb, nullptr);
    if (FAILED(hr))
    {
        Logger::Write(L"CDA: IDirectSound_CreateSoundBuffer %s\n", DXError(hr));
        return;
    }
    hr = pdsb->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*)&dsbuf);
    IDirectSound_Release(pdsb);
    if (FAILED(hr))
    {
        Logger::Write(L"CDA: Secondary QueryInterface() failure: %s\n", DXError(hr));
        return;
    }
    hr = dsbuf->QueryInterface(IID_IDirectSoundNotify, (LPVOID*)&dsnotify);
    if (FAILED(hr))
    {
        Logger::Write(L"CDA: IID_IDirectSoundNotify QueryInterface() failure: %s\n", DXError(hr));
        return;
    }

    notifyevent[0] = CreateEvent(nullptr, TRUE, FALSE, nullptr);
    notifyevent[1] = CreateEvent(nullptr, TRUE, FALSE, nullptr);
    DSBPOSITIONNOTIFY nf[2];
    nf[0].dwOffset = bufsize / num_sectors;
    nf[1].dwOffset = bufsize + bufsize / num_sectors;
    nf[0].hEventNotify = notifyevent[0];
    nf[1].hEventNotify = notifyevent[1];
    hr = dsnotify->SetNotificationPositions(2, nf);

    active = true;
    #else
    MMRESULT mmr;
    mmr = waveOutOpen(&wavehandle, WAVE_MAPPER, &wav, 0, 0, WAVE_ALLOWSYNC | WAVE_FORMAT_DIRECT);
    if (mmr != MMSYSERR_NOERROR)
    {
        Logger::Write(L"IMAGE CDDA: wave open %d\n", mmr);
        return;
    }
    this->num_sectors = num_sectors;
    for (int i = 0; i < 2; i++)
    {
        memset(&whdr[i], 0, sizeof WAVEHDR);
        whdr[i].dwBufferLength = 2352 * num_sectors;
        whdr[i].lpData = (LPSTR)buffers[i];
        mmr = waveOutPrepareHeader(wavehandle, &whdr[i], sizeof(WAVEHDR));
        if (mmr != MMSYSERR_NOERROR)
        {
            Logger::Write(L"IMAGE CDDA: waveOutPrepareHeader %d:%d\n", i, mmr);
            return;
        }
        whdr[i].dwFlags |= WHDR_DONE;
    }
    active = true;
    playing = true;
    #endif
}

void cda_audio::setvolume(int master, int left, int right)
{
    for (int j = 0; j < 2; j++)
    {
        volume[j] = j == 0 ? left : right;
        volume[j] = (100 - master) * volume[j] / 100;
        if (volume[j])
            volume[j]++;
        if (volume[j] >= 32768)
            volume[j] = 32768;
    }
    #if CDADS
    LONG vol = DSBVOLUME_MIN;
    int volume = master * left / 32768;
    if (volume < 100)
        vol = (LONG)((DSBVOLUME_MIN / 2) + (-DSBVOLUME_MIN / 2) * log(1 + (2.718281828 - 1) * (1 - volume / 100.0)));
    HRESULT hr = dsbuf->SetVolume(vol);
    if (FAILED(hr))
        Logger::Write(L"CDA: SetVolume(%d) failed: %s\n", vol, DXError(hr));
    #endif
}
bool cda_audio::play(int bufnum)
{
    if (!active)
        return false;
    #if CDADS
    DWORD status;
    HRESULT hr = dsbuf->GetStatus(&status);
    if (FAILED(hr))
    {
        Logger::Write(L"CDA: GetStatus() failed: %s\n", DXError(hr));
        return false;
    }
    if (status & DSBSTATUS_BUFFERLOST)
    {
        Logger::Write(L"CDA: bufferlost\n");
        return false;
    }
    if ((status & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING)) != (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING))
    {
        dsbuf->SetCurrentPosition((1 - bufnum) * bufsize);
        dsbuf->Play(0, 0, DSBPLAY_LOOPING);
        playing = true;
    }
    PVOID ptr;
    DWORD len;
    if (SUCCEEDED(dsbuf->Lock(bufnum * bufsize, bufsize, &ptr, &len, nullptr, nullptr, 0)))
    {
        memcpy(ptr, buffers[bufnum], bufsize);
        dsbuf->Unlock(ptr, len, nullptr, nullptr);
    }
    return true;
    #else
    short* p = (short*)(buffers[bufnum]);
    for (int i = 0; i < num_sectors * 2352 / 4; i++)
    {
        p[i * 2 + 0] = p[i * 2 + 0] * volume[0] / 32768;
        p[i * 2 + 1] = p[i * 2 + 1] * volume[1] / 32768;
    }
    MMRESULT mmr = waveOutWrite(wavehandle, &whdr[bufnum], sizeof(WAVEHDR));
    if (mmr != MMSYSERR_NOERROR)
    {
        Logger::Write(L"IMAGE CDDA: waveOutWrite %d\n", mmr);
        return false;
    }
    return true;
    #endif
}
void cda_audio::wait(int bufnum)
{
    if (!active || !playing)
        return;
    #if CDADS
    WaitForSingleObject(notifyevent[bufnum], INFINITE);
    ResetEvent(notifyevent[bufnum]);
    #else
    while (!(whdr[bufnum].dwFlags & WHDR_DONE))
        Sleep(10);
    #endif
}